// XomImpl.groovy
// Mac Radigan
// $Id: XomImpl.groovy 41 2011-10-30 12:31:30Z mac.radigan $
package org.radigan.xom.impl

import org.radigan.xom.service.Xom
import org.radigan.xom.service.Infoset
import org.radigan.xom.service.Object
import org.radigan.xom.service.XomException
import org.radigan.xom.service.AstNode

import java.io.File
import javax.xml.validation.Schema
import org.w3c.dom.Document
import javax.xml.parsers.SAXParserFactory
import javax.xml.parsers.ParserConfigurationException
import javax.xml.parsers.SAXParser
import org.xml.sax.XMLReader
import javax.xml.transform.dom.DOMResult
import javax.xml.transform.dom.DOMSource
import javax.xml.parsers.DocumentBuilder
import javax.xml.validation.Validator
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.validation.SchemaFactory
import javax.xml.transform.TransformerFactory
import org.xml.sax.helpers.XMLReaderFactory
import javax.xml.transform.stream.StreamResult
import javax.xml.XMLConstants
import javax.xml.validation.TypeInfoProvider
import javax.xml.validation.ValidatorHandler
//import org.apache.xerces.xs.PSVIProvider
import java.io.InputStream
import org.xml.sax.InputSource
import groovy.xml.Namespace

public class XomImpl implements Xom {
  static final String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage"
  static final String JAXP_SCHEMA_SOURCE = "http://java.sun.com/xml/jaxp/properties/schemaSource"
  static final boolean NAMESPACE_AWARE = true
  static final boolean SCHEMA_LOCATION = false
  static final boolean VALIDATING = true
  def saxParserFactory = null
  def schemaFactory = null
  def documentBuilderFactory = null
  def documentBuilder = null
  def transformerFactory = null
  def xmlReaderFactory = null
  def schema = null
  def validator = null
  def schemaList = ["note.xsd"]
  def abstractSyntaxTree = null
  def File instanceFile = null
  public XomImpl() {
    saxParserFactory = SAXParserFactory.newInstance()
    saxParserFactory.setValidating(VALIDATING)
    saxParserFactory.setNamespaceAware(NAMESPACE_AWARE)
    saxParserFactory.setXIncludeAware(true)
    schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI)
    documentBuilderFactory = DocumentBuilderFactory.newInstance()
    documentBuilderFactory.setNamespaceAware(NAMESPACE_AWARE)
    documentBuilderFactory.setAttribute(JAXP_SCHEMA_LANGUAGE, XMLConstants.W3C_XML_SCHEMA_NS_URI)
    documentBuilderFactory.setAttribute(JAXP_SCHEMA_SOURCE, getSchemaList() as String[])
    documentBuilder = documentBuilderFactory.newDocumentBuilder()
    transformerFactory = TransformerFactory.newInstance()
    xmlReaderFactory = XMLReaderFactory.newInstance()
  }
  InputStream toInputStream(Document document) {
    def byteArrayOutputStream = new ByteArrayOutputStream()
    def domSource = new DOMSource(document)
    def streamResult = new StreamResult(byteArrayOutputStream)
    transformerFactory.newTransformer().transform(domSource, streamResult)
    def inputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray())
    return inputStream
  }
  List getSchemaList() {
    return schemaList
  }
  File resolveFile(File file) {
    if(file.exists()) {
      return file
    } else if(!instanceFile) {
      throw new XomException("""Unable to locate instance document.""")
    } else {
      def testFile = new File("${instanceFile.getParent()}/${file.getName()}")
      if(testFile.exists()) return testFile
    }
    throw new XomException("""Unable to locate schema.""")
  }
  void resolveSchema(Document instance) throws XomException {
    if(!validator) {
      try {
        def xmlString = getAsString(instance)
        def xmlParser = new XmlParser(false, true).parseText(xmlString)  
        def xsi = new Namespace("http://www.w3.org/2001/XMLSchema-instance","xsi")
        def schemaFilename = null
        if(SCHEMA_LOCATION) {
          def schemaLocation = xmlParser.attributes()[xsi.schemaLocation]
          schemaFilename = schemaLocation.tokenize()[1]
        } else {
          schemaFilename = xmlParser.attributes()[xsi.noNamespaceSchemaLocation]
        }
        def schemaFile = new File(schemaFilename)
        setSchema(resolveFile(schemaFile))
      } catch(e) {
        throw new XomException("""Unable to resolve schema location.""")
      }
    }
  }
  String getAsString(Document instance) throws XomException {
    def transformer = transformerFactory.newTransformer()
    def stringWriter = new StringWriter()
    def streamResult = new StreamResult(stringWriter)
    def domSource = new DOMSource(instance)
    transformer.transform(domSource, streamResult);
    stringWriter.close()
    return stringWriter.toString()
  }
  void read(Document instance) throws XomException {
    resolveSchema(instance)
    def inputStream = null
    if(validator) {
      def domSource = new DOMSource(instance)
      def domResult = new DOMResult()
      try {
        validator.validate(domSource, domResult)
      } catch(e) {
        e.printStackTrace()
        println "VALIDATOR FAILED"
      }
      inputStream = toInputStream(domResult.getNode())
    } else {
      inputStream = toInputStream(instance)
    }
    //def saxParser = saxParserFactory.newSAXParser()
    def xmlReader = XMLReaderFactory.createXMLReader()
    //xmlReader.setFeature("http://xml.org/sax/features/validation", true)
    //xmlReader.setFeature("http://apache.org/xml/features/validation/schema", true)
    //xmlReader.setFeature("http://apache.org/xml/features/validation/schema-full-checking", true)
    xmlReader.setFeature("http://apache.org/xml/features/validation/schema/augment-psvi", true)
    //xmlReader.setProperty("http://apache.org/xml/properties/schema/external-schemaLocation", "note note.xsd")
    //saxParser.setProperty(JAXP_SCHEMA_LANGUAGE, XMLConstants.W3C_XML_SCHEMA_NS_URI)
    ValidatorHandler validatorHandler = schema.newValidatorHandler()
    TypeInfoProvider typeInfoProvider = validatorHandler.getTypeInfoProvider()
    def xomParser = new XomParserImpl(this)
    //xomParser.setPsviProvider((PSVIProvider)xmlReader)
    xomParser.setTypeInfoProvider(typeInfoProvider)
    validatorHandler.setContentHandler(xomParser)
    xmlReader.setContentHandler(validatorHandler)
    xmlReader.parse(new InputSource(inputStream))
    abstractSyntaxTree = xomParser.getAbstractSyntaxTree()
  }
  public String toString() throws XomException {
    return getAbstractSyntaxTree().toString()
  }
  public AstNode getAbstractSyntaxTree() throws XomException {
    return abstractSyntaxTree
  }
  public void read(File instance) {
    instanceFile = instance
    read(documentBuilder.parse(instance))
  }
  void setSchema(Schema schema) throws XomException {
    this.schema = schema
    if(VALIDATING) validator = schema.newValidator()
  }
  void setSchema(File schema) throws XomException {
    setSchema(schemaFactory.newSchema(schema))
  }
  Schema getSchema() {
    return schema
  }
  void write(File outputDirectory) {
  }
  org.radigan.xom.service.Object getObjectModel() {
    return null
  }
  Infoset getInfoset(String namespace) throws XomException {
    return null
  }
}

// *EOF*
